#include "head.h" // 包含头文件，定义了所需的函数和数据结构
#define SOCKET_NUM 500 // 定义最大支持的连接数

// 定义任务结构体，用于管理文件传输任务
typedef struct {
    int fd;       // 文件描述符，用于读取文件
    int cfd;      // 客户端文件描述符
    int number;   // 任务编号
    int pos;      // 当前传输的位置
    int end;      // 传输结束的位置
    int state;    // 任务状态
} Task;

// 定义BBQ结构体，用于描述文件分块信息
typedef struct {
    int start;        // 分块起始位置
    int end;          // 分块结束位置
    int number;       // 分块编号
    char md5sum[50];  // 分块的MD5校验值
} BBQ;

// 定义注册信息结构体
typedef struct {
    char name[30];       // 用户名
    char passward[100];  // 密码
    char token[50];      // 用户认证的Token
} Zhuce;

// 连接数据库
// 参数:
// conn - MySQL连接句柄的指针
int sql_connect(MYSQL **conn)
{
    char server[] = "localhost";  // 数据库服务器地址
    char user[] = "root";         // 数据库用户名
    char password[] = "123456";   // 数据库密码
    char database[] = "Netdisk";  // 数据库名称

    *conn = mysql_init(NULL); // 初始化MySQL连接
    if (!mysql_real_connect(*conn, server, user, password, database, 0, NULL, 0)) {
        // 如果连接失败，打印错误信息并退出
        // printf("Error connecting to database:%s\n",mysql_error(*conn));
        exit(0);
    } else {
        // printf("Connected...\n");
    }
    return 0;
}

// 验证用户Token
// 参数:
// conn - MySQL连接句柄
// name - 用户名
// token - 用户的Token
// 返回值: 0表示验证成功，-1表示验证失败
int math_token(MYSQL *conn, char *name, char *token)
{
    int ret = -1; // 默认返回失败
    MYSQL_RES *res; // 查询结果集
    MYSQL_ROW row;  // 查询结果行
    char query[300] = "select token from User where name='"; // 查询语句
    sprintf(query, "%s%s%s", query, name, "'"); // 拼接查询语句
    // puts(query);

    int t = mysql_query(conn, query); // 执行查询
    if (t) {
        printf("Error making query:%s\n", mysql_error(conn)); // 查询失败
    } else {
        // printf("Query made...\n");
        res = mysql_use_result(conn); // 获取查询结果
        if (res) {
            if ((row = mysql_fetch_row(res)) != NULL) {
                if (*row[0] == 0)
                    ret = -1; // Token为空，验证失败
                else if (strcmp(row[0], token) == 0) {
                    ret = 0; // Token匹配，验证成功
                }
            }
        } else {
            printf("查询出错\n");
            exit(0);
        }
        mysql_free_result(res); // 释放查询结果
    }
    return ret;
}

// epoll操作函数
// 参数:
// epfd - epoll实例的文件描述符
// fd - 要操作的文件描述符
// caozuo - 操作类型（EPOLL_CTL_ADD、EPOLL_CTL_DEL等）
// duxie - 监听的事件类型（EPOLLIN、EPOLLOUT等）
void epoll_func(int epfd, int fd, int caozuo, int duxie)
{
    struct epoll_event event;
    event.events = duxie; // 设置监听事件类型
    event.data.fd = fd;   // 设置文件描述符
    epoll_ctl(epfd, caozuo, fd, &event); // 执行epoll操作
}

// 接收固定长度的数据
// 参数:
// fd - 文件描述符
// pbuf - 数据缓冲区
// len - 要接收的数据长度
// 返回值: 0表示成功，-1表示失败
int recvCYL(int fd, void *pbuf, int len)
{
    char *buf = (char*)pbuf; // 转换为字符指针
    int total = 0, ret; // total记录已接收的数据长度
    while (total < len) {
        ret = recv(fd, buf + total, len - total, 0); // 接收数据
        if (ret == 0) {
            return -1; // 客户端断开连接
        }
        total += ret; // 累加接收的数据长度
    }
    return 0;
}

// 接收一个完整的数据包
// 参数:
// fd - 文件描述符
// ptrain - 数据包结构体指针
// 返回值: 0表示成功，-1表示失败
int one_recv(int fd, Train_t *ptrain)
{
    int ret = recvCYL(fd, &ptrain->Len, 4); // 接收数据长度
    ERROR_CHECK(ret, -1, "client quit");
    ret = recvCYL(fd, &ptrain->ctl_code, 4); // 接收控制码
    ERROR_CHECK(ret, -1, "client quit");
    ret = recvCYL(fd, &ptrain->buf, ptrain->Len); // 接收数据内容
    ERROR_CHECK(ret, -1, "client quit");
    return 0;
}

// 断开客户端连接
// 参数:
// pk - 任务结构体指针
// epfd - epoll实例的文件描述符
void disconnect(Task *pk, int epfd)
{
    if (pk->fd > 0)
        close(pk->fd); // 关闭文件描述符
    epoll_func(epfd, pk->cfd, EPOLL_CTL_DEL, EPOLLOUT); // 从epoll中移除写事件
    epoll_func(epfd, pk->cfd, EPOLL_CTL_DEL, EPOLLIN);  // 从epoll中移除读事件
    printf("已经断开连接\n");
    bzero(pk, sizeof(Task)); // 清空任务结构体
}

// 添加文件传输任务
// 参数:
// pt - 任务结构体指针
// pb - 文件分块信息结构体指针
// epfd - epoll实例的文件描述符
void add_task(Task *pt, BBQ *pb, int epfd)
{
    char path[100] = {0}; // 文件路径缓冲区
    strcpy(path, DOWN_PATH); // 设置下载路径
    sprintf(path, "%s%s", path, pb->md5sum); // 拼接文件路径
    pt->fd = open(path, O_RDONLY); // 打开文件
    printf("fd = %d\n", pt->fd);
    lseek(pt->fd, pb->start, SEEK_SET); // 设置文件读取起始位置
    pt->end = pb->end; // 设置文件读取结束位置
    pt->pos = pb->start; // 设置当前读取位置
    pt->number = pb->number; // 设置任务编号
    epoll_func(epfd, pt->cfd, EPOLL_CTL_DEL, EPOLLIN); // 从epoll中移除读事件
    epoll_func(epfd, pt->cfd, EPOLL_CTL_ADD, EPOLLOUT); // 添加写事件到epoll
    printf("改变成功\n");
}

int main()
{
    int socketFd; // 服务器监听套接字
    socketFd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字
    ERROR_CHECK(socketFd, -1, "socket"); // 检查套接字创建是否成功

    struct sockaddr_in ser; // 定义服务器地址结构体
    bzero(&ser, sizeof(ser)); // 清空结构体
    ser.sin_family = AF_INET; // 设置地址族为IPv4
    ser.sin_port = htons(3000); // 设置端口号为3000（网络字节序）
    ser.sin_addr.s_addr = inet_addr("0"); // 设置IP地址为0（监听所有地址）

    int ret;
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser)); // 绑定地址和端口
    ERROR_CHECK(ret, -1, "bind"); // 检查绑定是否成功

    listen(socketFd, 100); // 开始监听，最大连接队列长度为100

    MYSQL *conn; // MySQL连接句柄
    sql_connect(&conn); // 连接数据库

    Task task[SOCKET_NUM]; // 定义任务数组，用于管理客户端连接
    bzero(task, SOCKET_NUM * sizeof(Task)); // 初始化任务数组

    Train_t train; // 数据传输结构体
    Zhuce info; // 用户注册信息结构体
    BBQ bq; // 文件分块信息结构体
    int ready_num, i, j; // ready_num: 就绪事件数量，i/j: 循环变量

    int epfd = epoll_create(1); // 创建epoll实例
    struct epoll_event evs[SOCKET_NUM]; // 定义epoll事件数组
    epoll_func(epfd, socketFd, EPOLL_CTL_ADD, EPOLLIN); // 将监听套接字加入epoll监听

    while (1) // 主循环
    {
        ready_num = epoll_wait(epfd, evs, SOCKET_NUM, -1); // 等待epoll事件
        usleep(1000); // 防止CPU占用过高，稍作延迟

        for (i = 0; i < ready_num; i++) // 遍历所有就绪事件
        {
            // 处理新连接请求
            if (evs[i].events == EPOLLIN && evs[i].data.fd == socketFd)
            {  
                printf("连接请求\n");
                for (j = 0; j < SOCKET_NUM; j++) // 查找空闲任务槽位
                    if (task[j].cfd == 0)
                        break;
                if (j == SOCKET_NUM) // 如果没有空闲槽位，忽略连接
                    continue;

                task[j].cfd = accept(socketFd, NULL, NULL); // 接受新连接
                task[j].state = 1; // 设置任务状态为已连接
                epoll_func(epfd, task[j].cfd, EPOLL_CTL_ADD, EPOLLIN); // 将新连接加入epoll监听
                continue;
            }

            // 处理客户端请求
            for (j = 0; j < SOCKET_NUM; j++)
            {
                if (evs[i].events == EPOLLIN && evs[i].data.fd == task[j].cfd)
                {
                    ret = one_recv(task[j].cfd, &train); // 接收客户端数据
                    if (ret == -1) // 如果接收失败，断开连接
                    {
                        disconnect(task + j, epfd);
                        break;
                    }

                    if (train.ctl_code == 1) // Token认证请求
                    {
                        printf("token认证\n");
                        memcpy(&info, train.buf, train.Len); // 解析客户端发送的认证信息
                        ret = math_token(conn, info.name, info.token); // 验证Token
                        printf("ret = %d\n", ret);
                        if (ret == -1) // 验证失败
                            break;
                        else
                            task[j].state = 2; // 验证成功，更新任务状态
                        printf("name: %s\ntoken %s\n", info.name, info.token);
                    }
                    else // 文件下载请求
                    {
                        printf("下载任务发送\n");
                        if (task[j].state != 2) // 如果未通过认证，断开连接
                        {
                            disconnect(task + j, epfd);
                            break;
                        }
                        memcpy(&bq, train.buf, train.Len); // 解析文件分块信息
                        // printf("number =%d,start =%d,end= %d\nmd5sum= %s\n",
                        //        bq.number,bq.start,bq.end,bq.md5sum);
                        add_task(task + j, &bq, epfd); // 添加文件传输任务
                    }
                }

                // 处理文件传输
                if (evs[i].events == EPOLLOUT && evs[i].data.fd == task[j].cfd)
                {
                    if (task[j].pos < task[j].end) // 如果未传输完成
                    {
                        ret = read(task[j].fd, train.buf, BUFSIZE); // 从文件读取数据
                        train.Len = ret; // 设置数据长度
                        train.ctl_code = task[j].number; // 设置任务编号
                        ret = send(task[j].cfd, &train, train.Len + 8, 0); // 发送数据
                        if (ret == -1) // 如果发送失败，断开连接
                            disconnect(task + j, epfd);
                    }
                    else // 如果传输完成
                    {
                        train.ctl_code = task[j].number; // 设置任务编号
                        train.Len = 0; // 设置数据长度为0
                        send(task[j].cfd, &train, train.Len + 8, 0); // 发送传输完成标志
                        printf("第%d段文件下载完成\n", train.ctl_code);
                        disconnect(task + j, epfd); // 断开连接
                    }
                }
            }
        }
    }
    return 0; // 程序正常退出
}

